package com.sneakxy.mybatis.commons.repository.support;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
import org.springframework.data.repository.query.QueryByExampleExecutor;

import com.alibaba.fastjson2.JSON;
import com.sneakxy.mybatis.commons.orm.MybatisEntityManager;
import com.sneakxy.mybatis.commons.repository.CriteriaRepository;
import com.sneakxy.mybatis.commons.repository.ExpandRepository;
import com.sneakxy.mybatis.commons.repository.query.domain.Criteria;
import com.sneakxy.mybatis.commons.repository.query.executor.CountRepositoryExecutor;
import com.sneakxy.mybatis.commons.repository.query.executor.CriteriaDeleteRepositoryExecutor;
import com.sneakxy.mybatis.commons.repository.query.executor.CriteriaQueryRepsoitoryExecutor;
import com.sneakxy.mybatis.commons.repository.query.executor.CriteriaUpdateRepositoryExecutor;
import com.sneakxy.mybatis.commons.repository.query.executor.DeleteAllRepositoryExecutor;
import com.sneakxy.mybatis.commons.repository.query.executor.DeleteRepositoryExecutor;
import com.sneakxy.mybatis.commons.repository.query.executor.ExistsRepositoryExecutor;
import com.sneakxy.mybatis.commons.repository.query.executor.FindAllByIdsRepositoryExecutor;
import com.sneakxy.mybatis.commons.repository.query.executor.FindAllPageRepositoryExecutor;
import com.sneakxy.mybatis.commons.repository.query.executor.FindAllRepositoryExecutor;
import com.sneakxy.mybatis.commons.repository.query.executor.FindOneRepositoryExecutor;
import com.sneakxy.mybatis.commons.repository.query.executor.RepositoryExecutor;
import com.sneakxy.mybatis.commons.repository.query.executor.SaveRepositoryExecutor;
import com.sneakxy.mybatis.commons.repository.support.transformer.DefaultFetchableFluentQuery;

/**
 * @author 潜行的青衣
 */
public class MybatisSimpleRepository<T, ID extends Serializable> 
	implements CrudRepository<T, ID>, PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T>, 
		CriteriaRepository<T, ID>, ExpandRepository<T, ID>, InitializingBean {
	
	private MybatisEntityManager entityManager;
	
	public MybatisSimpleRepository(
			MybatisEntityManager entityManager) {
		this.entityManager = entityManager;
	}
	
	private RepositoryExecutor<T> saveExecutor;
	
	private RepositoryExecutor<T> criteriaUpdateExecutor;
	
	private RepositoryExecutor<T> findOneExecutor;
	
	private RepositoryExecutor<T> findOneCriteriaExecutor;
	
	private RepositoryExecutor<Boolean> existsExecutor;
	
	private RepositoryExecutor<List<T>> findAllExecutor;
	
	private RepositoryExecutor<List<T>> findAllSortExecutor;
	
	private RepositoryExecutor<Page<T>> findAllPageExecutor;
	
	private RepositoryExecutor<List<T>> findAllByIdsExecutor;
	
	private RepositoryExecutor<List<T>> findAllCriteriaExecutor;
	
	private RepositoryExecutor<List<T>> findAllSortCriteriaExecutor;
	
	private RepositoryExecutor<Page<T>> findAllPageCriteriaExecutor;
	
	private RepositoryExecutor<Long> countExecutor;
	
	private RepositoryExecutor<Long> countCriteriaExecutor;
	
	private RepositoryExecutor<Long> deleteExecutor;
	
	private RepositoryExecutor<Long> deleteCriteriaExecutor;
	
	private RepositoryExecutor<Long> deleteAllExecutor;
	
	@Override
	public String getTableName() {
		return getEntityManager().getTablename();
	}

	/* (non-Javadoc)
	 * @see org.springframework.data.repository.CrudRepository#save(java.lang.Object)
	 */
	@Override
	public <S extends T> S save(S entity) {
		return save(entity, getTableName());
	}

	@Override
	public <S extends T> S save(S entity, String tableName) {
		this.saveExecutor.execute(entity, tableName);
		return entity;
	}

	@Override
	public <S extends T> void update(S entity, Criteria<S> criteria) {
		update(entity, getTableName(), criteria);
	}

	@Override
	public <S extends T> void update(S entity, String tableName, Criteria<S> criteria) {
		criteriaUpdateExecutor.execute(entity, tableName, criteria);
	}

	/* (non-Javadoc)
	 * @see org.springframework.data.repository.CrudRepository#save(java.lang.Iterable)
	 */
	@Override
	public <S extends T> Iterable<S> saveAll(Iterable<S> entities) {
		entities.forEach((entity) -> {
			save(entity);
		});
		return entities;
	}

	/* (non-Javadoc)
	 * @see org.springframework.data.repository.CrudRepository#findOne(java.io.Serializable)
	 */
	@Override
	public Optional<T> findById(ID id) {
		return findById(id, getTableName());
	}

	@Override
	public Optional<T> findById(ID id, String table_name) {
		return Optional.ofNullable(findOneExecutor.execute(id, table_name));
	}

	/* (non-Javadoc)
	 * @see org.springframework.data.repository.CrudRepository#exists(java.io.Serializable)
	 */
	@Override
	public boolean existsById(ID id) {
		return existsById(id, getTableName());
	}

	@Override
	public boolean existsById(ID id, String table_name) {
		return existsExecutor.execute(id, table_name);
	}
	
	@Override
	public boolean exists(Criteria<T> criteria, String table_name) {
		return count(criteria, table_name) > 0;
	}

	@Override
	public boolean exists(Criteria<T> criteria) {
		return exists(criteria, getTableName());
	}

	/* (non-Javadoc)
	 * @see org.springframework.data.repository.CrudRepository#findAll()
	 */
	@Override
	public Iterable<T> findAll() {
		return findAll(getTableName());
	}

	@Override
	public Iterable<T> findAll(String table_name) {
		return findAllExecutor.execute(table_name);
	}

	/* (non-Javadoc)
	 * @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable)
	 */
	@Override
	public Iterable<T> findAllById(Iterable<ID> ids) {
		return this.findAllByIdsExecutor.execute(ids);
	}

	/* (non-Javadoc)
	 * @see org.springframework.data.repository.CrudRepository#count()
	 */
	@Override
	public long count() {
		return count(getTableName());
	}


	@Override
	public long count(String table_name) {
		Long count = countExecutor.execute(table_name);
		return count != null ? count : 0;
	}

	/* (non-Javadoc)
	 * @see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable)
	 */
	@Override
	public void deleteById(ID id) {
		deleteById(id, getTableName());
	}

	@Override
	public void deleteById(ID id, String table_name) {
		this.deleteExecutor.execute(id, table_name);
	}

	@Override
	public void deleteAllById(Iterable<? extends ID> ids) {
		ids.forEach((id) -> {
			deleteById(id);
		});
		
	}

	/* (non-Javadoc)
	 * @see org.springframework.data.repository.CrudRepository#delete(java.lang.Object)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void delete(T entity) {
		ID id = (ID) this.getEntityManager().getEntityInformation().getId(entity);
		this.deleteById(id);
	}

	/* (non-Javadoc)
	 * @see org.springframework.data.repository.CrudRepository#delete(java.lang.Iterable)
	 */
	@Override
	public void deleteAll(Iterable<? extends T> entities) {
		entities.forEach((entity) -> {
			delete(entity);
		});
	}

	@Override
	public void delete(Criteria<T> criteria, String table_name) {
		this.deleteCriteriaExecutor.execute(criteria, table_name);
	}

	@Override
	public void delete(Criteria<T> criteria) {
		delete(criteria, getTableName());
	}

	/* (non-Javadoc)
	 * @see org.springframework.data.repository.CrudRepository#deleteAll()
	 */
	@Override
	public void deleteAll() {
		deleteAll(getTableName());
	}

	@Override
	public void deleteAll(String table_name) {
		this.deleteAllExecutor.execute(table_name);
	}

	/* (non-Javadoc)
	 * @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Sort)
	 */
	@Override
	public Iterable<T> findAll(Sort sort) {
		return findAll(getTableName(), sort);
	}

	@Override
	public Iterable<T> findAll(String table_name, Sort sort) {
		return this.findAllSortExecutor.execute(table_name, sort);
	}

	/* (non-Javadoc)
	 * @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Pageable)
	 */
	@Override
	public Page<T> findAll(Pageable pageable) {
		return findAll(getTableName(), pageable);
	}

	@Override
	public Page<T> findAll(String table_name, Pageable pageable) {
		return this.findAllPageExecutor.execute(table_name, pageable);
	}

	/* (non-Javadoc)
	 * @see org.springframework.data.repository.query.QueryByExampleExecutor#findOne(org.springframework.data.domain.Example)
	 */
	@Override
	public <S extends T> Optional<S> findOne(Example<S> example) {
		return findOne(example, null);
	}
	
	@SuppressWarnings("unchecked")
	public <S extends T> Optional<S> findOne(Example<S> example, Collection<String> properties) {
		MybatisExampleAccessor<T> accessor =new MybatisExampleAccessor<T>(
				(Example<T>)example, properties,
				entityManager.getConfiguration().getTypeHandlerRegistry());
		try {
			return Optional.ofNullable((S) this.findOne(accessor.toCriteria()));
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	

	/* (non-Javadoc)
	 * @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example)
	 */
	@Override
	public <S extends T> Iterable<S> findAll(Example<S> example) {
		return findAll(example, (Collection<String>) null);
	}
	
	@SuppressWarnings("unchecked")
	public <S extends T> Iterable<S> findAll(Example<S> example, Collection<String> properties) {
		MybatisExampleAccessor<T> accessor = new MybatisExampleAccessor<T>(
				(Example<T>)example, properties, 
				entityManager.getConfiguration().getTypeHandlerRegistry());
		try {
			return (Iterable<S>) this.findAll(accessor.toCriteria());
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example, org.springframework.data.domain.Sort)
	 */
	@Override
	public <S extends T> Iterable<S> findAll(Example<S> example, Sort sort) {
		return findAll(example, null, sort);
	}
	
	@SuppressWarnings("unchecked")
	public <S extends T> Iterable<S> findAll(Example<S> example, Collection<String> properties, Sort sort) {
		MybatisExampleAccessor<T> accessor = new MybatisExampleAccessor<>(
				(Example<T>)example, properties,
				entityManager.getConfiguration().getTypeHandlerRegistry());
		try {
			return (Iterable<S>) this.findAll(accessor.toCriteria(), sort);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example, org.springframework.data.domain.Pageable)
	 */
	@Override
	public <S extends T> Page<S> findAll(Example<S> example, Pageable pageable) {
		return findAll(example, null, pageable);
	}
	
	@SuppressWarnings("unchecked")
	public <S extends T> Page<S> findAll(Example<S> example, Collection<String> properties, Pageable pageable) {
		MybatisExampleAccessor<T> accessor = new MybatisExampleAccessor<>(
				(Example<T>)example, properties,
				entityManager.getConfiguration().getTypeHandlerRegistry());
		try {
			return (Page<S>) this.findAll(accessor.toCriteria(), pageable);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	public static class ExampleFetchableFluentQuery<T, S extends T, ID extends Serializable> implements FetchableFluentQuery<S> {
		
		private Example<S> example;
		
		private MybatisSimpleRepository<T, ID> repository;
		
		private Collection<String> properties;
		
		private Sort sort;
		
		public ExampleFetchableFluentQuery(MybatisSimpleRepository<T, ID> repository, Example<S> example) {
			this.example = example;
			this.repository = repository;
		}
		
		@Override
		public FetchableFluentQuery<S> sortBy(Sort sort) {
			this.sort = sort;
			return this;
		}

		@Override
		public <R> FetchableFluentQuery<R> as(final Class<R> resultType) {
			final ExampleFetchableFluentQuery<T, S, ID> _this = this;
			
			return new DefaultFetchableFluentQuery<S, R>() {
				
				private Function<S, R> converter = (entity) -> {
					return JSON.copyTo(entity, resultType);
				};
				
				public FetchableFluentQuery<R> convert(Function<S, R> converter) {
					this.converter = converter;
					return this;
				}
				
				@Override
				public FetchableFluentQuery<R> sortBy(Sort sort) {
					_this.sortBy(sort);
					return this;
				}

				@Override
				public <RT> FetchableFluentQuery<RT> as(Class<RT> resultType) {
					return _this.as(resultType);
				}

				@Override
				public FetchableFluentQuery<R> project(Collection<String> properties) {
					_this.project(properties);
					return this;
				}

				@Override
				public R oneValue() {
					S result = _this.oneValue();
					return converter.apply(result);
				}

				@Override
				public R firstValue() {
					S result = _this.firstValue();
					return converter.apply(result);
				}

				@Override
				public List<R> all() {
					List<S> list = _this.all();
					List<R> result = new ArrayList<>(list.size());
					list.forEach((entity) -> {
						result.add(converter.apply(entity));
					});
					return result;
				}

				@Override
				public Page<R> page(Pageable pageable) {
					Page<S> page = _this.page(pageable);
					Page<R> result = page.map((conent) -> {
						return converter.apply(conent);
					});
					return result;
				}

				@Override
				public Stream<R> stream() {
					return all().stream();
				}

				@Override
				public long count() {
					return _this.count();
				}

				@Override
				public boolean exists() {
					return _this.exists();
				}
				
			};
		}

		@Override
		public FetchableFluentQuery<S> project(Collection<String> properties) {
			this.properties = properties;
			return this;
		}

		@Override
		public S oneValue() {
			Optional<S> result = repository.findOne(example, properties);
			return result.orElse(null);
		}

		@Override
		public S firstValue() {
			Iterable<S> list = null;
			if(sort != null) {
				list = repository.findAll(example, properties, PageRequest.of(0, 1, sort));
			} else {
				list = repository.findAll(example, properties, PageRequest.of(0, 1));
			}
			return Optional.ofNullable(list)
					.map(iterable -> iterable.iterator().next())
					.orElse(null);
		}

		@Override
		public List<S> all() {
			List<S> result = new ArrayList<>();
			Iterable<S> list = null;
			if(sort != null) {
				list = repository.findAll(example, properties, sort);
			} else {
				list = repository.findAll(example, properties);
			}
			list.forEach((entity) -> {
				result.add(entity);
			});
			return result;
		}

		@Override
		public Page<S> page(Pageable pageable) {
			return repository.findAll(example, properties, pageable);
		}

		@Override
		public Stream<S> stream() {
			return all().stream();
		}

		@Override
		public long count() {
			return repository.count(example, properties);
		}

		@Override
		public boolean exists() {
			return repository.exists(example, properties);
		}

		public Collection<String> getProperties() {
			return properties;
		}

		public Sort getSort() {
			return sort;
		}
		
	}
	
	@Override
	public <S extends T, R> R findBy(Example<S> example, Function<FetchableFluentQuery<S>, R> queryFunction) {
		return queryFunction.apply(new ExampleFetchableFluentQuery<>(this, example));
	}

	/* (non-Javadoc)
	 * @see org.springframework.data.repository.query.QueryByExampleExecutor#count(org.springframework.data.domain.Example)
	 */
	@Override
	public <S extends T> long count(Example<S> example) {
		return count(example, null);
	}
	
	@SuppressWarnings("unchecked")
	public <S extends T> long count(Example<S> example, Collection<String> properties) {
		MybatisExampleAccessor<T> accessor = new MybatisExampleAccessor<T>(
				(Example<T>) example, properties,
				entityManager.getConfiguration().getTypeHandlerRegistry());
		try {
			return this.count(accessor.toCriteria());
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return 0;
	}

	/* (non-Javadoc)
	 * @see org.springframework.data.repository.query.QueryByExampleExecutor#exists(org.springframework.data.domain.Example)
	 */
	@Override
	public <S extends T> boolean exists(Example<S> example) {
		return this.count(example) > 0;
	}
	
	public <S extends T> boolean exists(Example<S> example, Collection<String> properties) {
		return this.count(example, properties) > 0;
	}

	/* (non-Javadoc)
	 * @see com.sneakxy.mybatis.commons.repository.support.CriteriaRepository#findOne(com.sneakxy.mybatis.commons.repository.query.domain.Criteria)
	 */
	@Override
	public T findOne(Criteria<T> criteria) {
		return findOne(criteria, getTableName());
	}

	@Override
	public T findOne(Criteria<T> criteria, String table_name) {
		return this.findOneCriteriaExecutor.execute(criteria, table_name);
	}

	/* (non-Javadoc)
	 * @see com.sneakxy.mybatis.commons.repository.support.CriteriaRepository#findAll(com.sneakxy.mybatis.commons.repository.query.domain.Criteria)
	 */
	@Override
	public List<T> findAll(Criteria<T> criteria) {
		return findAll(criteria, getTableName());
	}

	@Override
	public List<T> findAll(Criteria<T> criteria, String table_name) {
		return this.findAllCriteriaExecutor.execute(criteria, table_name);
	}

	/* (non-Javadoc)
	 * @see com.sneakxy.mybatis.commons.repository.support.CriteriaRepository#findAll(com.sneakxy.mybatis.commons.repository.query.domain.Criteria, org.springframework.data.domain.Pageable)
	 */
	@Override
	public Page<T> findAll(Criteria<T> criteria, Pageable pageable) {
		return findAll(criteria, getTableName(),pageable);
	}

	@Override
	public Page<T> findAll(Criteria<T> criteria, String table_name, Pageable pageable) {
		return findAllPageCriteriaExecutor.execute(criteria, table_name, pageable);
	}

	/* (non-Javadoc)
	 * @see com.sneakxy.mybatis.commons.repository.support.CriteriaRepository#findAll(com.sneakxy.mybatis.commons.repository.query.domain.Criteria, org.springframework.data.domain.Sort)
	 */
	@Override
	public List<T> findAll(Criteria<T> criteria, Sort sort) {
		return findAll(criteria, getTableName(),sort);
	}

	@Override
	public List<T> findAll(Criteria<T> criteria, String table_name, Sort sort) {
		return findAllSortCriteriaExecutor.execute(criteria, table_name, sort);
	}

	/* (non-Javadoc)
	 * @see com.sneakxy.mybatis.commons.repository.support.CriteriaRepository#count(com.sneakxy.mybatis.commons.repository.query.domain.Criteria)
	 */
	@Override
	public long count(Criteria<T> criteria) {
		return count(criteria, getTableName());
	}
	
	@Override
	public long count(Criteria<T> criteria, String table_name) {
		return countCriteriaExecutor.execute(criteria, table_name);
	}
	
	@Override
	@SuppressWarnings("unchecked")
	public boolean isNew(T entity) {
		return getEntityManager().getEntityInformation().isNew(entity);
	}

	public MybatisEntityManager getEntityManager() {
		return entityManager;
	}

	/* (non-Javadoc)
	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
	 */
	@SuppressWarnings("unchecked")
	@Override
	public void afterPropertiesSet() throws Exception {
		//初始化保存
		this.saveExecutor = new SaveRepositoryExecutor<>(getEntityManager(), entityManager.getConfiguration().getSnowflakeIdGenerator(), this.getClass(), "save", this.getClass(), String.class);
		//更新
		this.criteriaUpdateExecutor = new CriteriaUpdateRepositoryExecutor<>(getEntityManager(), getClass(), "update", getClass(), String.class, Criteria.class);
		Class<ID> idClass = this.getEntityManager().getEntityInformation().getIdType();
		//初始化findOne
		this.findOneExecutor = new FindOneRepositoryExecutor<>(entityManager, this.getClass(), "findById", idClass, String.class);
		this.findOneCriteriaExecutor = new CriteriaQueryRepsoitoryExecutor<>(entityManager, this.getClass(), "findOne", Criteria.class, String.class);
		//初始化exist
		this.existsExecutor = new ExistsRepositoryExecutor(entityManager, this.getClass(), "existsById", idClass, String.class);
		//初始化findAll
		this.findAllExecutor = new FindAllRepositoryExecutor<>(entityManager, this.getClass(), "findAll", String.class);
		this.findAllSortExecutor = new FindAllRepositoryExecutor<>(entityManager, this.getClass(), "findAll", String.class, Sort.class);
		this.findAllByIdsExecutor = new FindAllByIdsRepositoryExecutor<>(entityManager, this.getClass(), "findAllById", Iterable.class);
		this.findAllPageExecutor = new FindAllPageRepositoryExecutor<>(entityManager, this.getClass(), "findAll", String.class, Pageable.class);
		
		this.findAllCriteriaExecutor = new CriteriaQueryRepsoitoryExecutor<>(entityManager, this.getClass(), "findAll", Criteria.class, String.class);
		this.findAllSortCriteriaExecutor = new CriteriaQueryRepsoitoryExecutor<>(entityManager, this.getClass(), "findAll", Criteria.class, String.class, Sort.class);
		this.findAllPageCriteriaExecutor = new CriteriaQueryRepsoitoryExecutor<>(entityManager, this.getClass(), "findAll", Criteria.class, String.class, Pageable.class);
		//初始化count
		this.countExecutor = new CountRepositoryExecutor(entityManager, this.getClass(), "count", String.class);
		this.countCriteriaExecutor = new CriteriaQueryRepsoitoryExecutor<>(entityManager, this.getClass(), "count", Criteria.class, String.class);
		//初始化delete
		this.deleteExecutor = new DeleteRepositoryExecutor(entityManager, this.getClass(), "deleteById", idClass, String.class);
		this.deleteCriteriaExecutor = new CriteriaDeleteRepositoryExecutor(entityManager, this.getClass(), "delete", Criteria.class, String.class);
		this.deleteAllExecutor = new DeleteAllRepositoryExecutor(entityManager, this.getClass(), "deleteAll", String.class);
	}

}
